ChunkManager = class()
ChunkManager.MaxLoadChunkCount = 64
ChunkManager.LoadChunkDistance = 8
ChunkManager.MaxUpdatePerTick = 1

function ChunkManager:init(world,worldInfo)
    self.world = world
    self.seed = world.seed
    self.length = self.world.length
    self.chunkLength = self.length // 16
    self.chunkOffset = self.chunkLength * self.chunkLength // 2 + self.chunkLength // 2 + 1
    self.chunkCount = self.length * self.length
    self.x0 = -self.chunkLength // 2
    self.x1 = self.chunkLength // 2 - 1
    self.mode = worldInfo.mode
    self.LoadChunkDistance = GameSetting.ViewChunkDistance + 2
    if self.chunkLength <= GameSetting.ViewChunkDistance then
        self.hasEnoughChunk = true
    else
        self.hasEnoughChunk = false
    end
    
    self.chunkGenerator = ChunkGenerator(world,self,self.seed)
    self.biomeNoise = NoiseMap(self.seed - 1,0.01,1,0.2)
    self.grassNoise = NoiseMap(self.seed - 2,0.1,1,0.2)
    self.chunks = {}
    self.readMode = false
    
    if self.mode == WorldMode.Flat then
        self.flatSample = worldInfo.flatSample
    end
end

function ChunkManager:createChunks()
    local centerX,centerZ = self.world.spawnX // 16,self.world.spawnZ // 16
    local cx0,cx1,cz0,cz1 = centerX - self.LoadChunkDistance // 2,centerX + self.LoadChunkDistance // 2 - 1,centerZ - self.LoadChunkDistance // 2,centerZ + self.LoadChunkDistance // 2 - 1
    for x = cx0,cx1 do
        for z = cz0,cz1 do
            self:loadChunk(x,z)
        end
    end
    for k,chunk in pairs(self.chunks) do
        if chunk.x >= cx0 + 1 and chunk.x <= cx1 - 1 and chunk.z >= cz0 + 1 and chunk.z <= cz1 - 1 then
            worldRenderer:bindChunk(chunk)
            chunk.bindedRenderChunk = true
        end
    end
    self.readMode = true
end

function ChunkManager:updateChunk()
    if self.hasEnoughChunk then return end
    self.updateFinished = false
    local centerX,centerZ = math.floor(player.lastPos.x // 16),math.floor(player.lastPos.z // 16)
    local cx0,cx1,cz0,cz1 = centerX - self.LoadChunkDistance // 2,centerX + self.LoadChunkDistance // 2 - 1,centerZ - self.LoadChunkDistance // 2,centerZ + self.LoadChunkDistance // 2 - 1
    for k,v in pairs(self.chunks) do
        if v.x < cx0 or v.x > cx1 or v.z < cz0 or v.z > cz1 then
            self:unloadChunk(v.x,v.z)
        end
    end
    local i = 0
    for x = cx0,cx1 do
        for z = cz0,cz1 do
            if i >= self.MaxUpdatePerTick then
                return
            end
            if self:hasChunk(x,z) and not self:isChunkLoaded(x,z) then
                self:loadChunk(x,z)
                i = i + 1
                if player.posUpdated then return end
            end
        end
    end
    self:updateChunkBinded(centerX,centerZ)
    self.updateFinished = true
end

function ChunkManager:updateChunkBinded(centerX,centerZ)
    local cx0,cx1,cz0,cz1 = centerX - GameSetting.ViewChunkDistance // 2,centerX + GameSetting.ViewChunkDistance // 2 - 1,centerZ - GameSetting.ViewChunkDistance // 2,centerZ + GameSetting.ViewChunkDistance // 2 - 1
    for k,chunk in pairs(self.chunks) do
        if chunk.x >= cx0 and chunk.x <= cx1 and chunk.z >= cz0 and chunk.z <= cz1 then
            if not chunk.bindedRenderChunk then
                worldRenderer:bindChunk(chunk)
                chunk.bindedRenderChunk = true
            end
        else
            if chunk.bindedRenderChunk then
                worldRenderer:unbindChunk(chunk.x,chunk.z)
                chunk.bindedRenderChunk = false
            end
        end
    end
end

function ChunkManager:loadChunk(x,z)
    if not self:hasChunk(x,z) then return end
    local index = x * self.chunkLength + z + self.chunkOffset
    local chunk
    if self.readMode and false then
        chunk = worldSaver:readChunk(index)
        if chunk then
            self.chunks[index] = chunk
            chunk.readyToRender = true
            return
        end
    end
    chunk = Chunk(self.world,x,z)
    if self.mode == WorldMode.Normal then
        self.chunkGenerator:generateChunk(chunk,index)
        Thread.waitWhenGaming()
        self.world:updateBrightness(chunk.xPos,chunk.zPos,16,16)
    else
        self.chunks[index] = chunk
        self.chunkGenerator:generateFlatChunk(chunk,self.flatSample)
    end
    chunk.readyToRender = true
    Thread.waitWhenGaming()
end

function ChunkManager:unloadChunk(x,z)
    local index = x * self.chunkLength + z + self.chunkOffset
    local chunk = self.chunks[index]
    if chunk then
        self.chunks[index] = nil
        -- 通知渲染器卸载渲染区块！
        if chunk.bindedRenderChunk then
            worldRenderer:unbindChunk(x,z)
        end
        -- worldSaver:saveChunk(chunk,index)
    end
end

function ChunkManager:hasChunk(x,z)
    if x < self.x0 or x > self.x1 or z < self.x0 or z > self.x1 then
        return false
    else
        return true
    end
end

function ChunkManager:isChunkLoaded(x,z)
    local chunk = self.chunks[x * self.chunkLength + z + self.chunkOffset]
    if chunk then
        return true
    else
        return false
    end
end

function ChunkManager:decorate()
    if self.mode == WorldMode.Normal then
        local count = self.world.length * self.world.length * 0.1
        TemplateGen(self.world):genTemplate(count * 0.05,TreeTemplate()):genTemplate(count * 0.05,OreTemplate())
    end
end

-- 用区块坐标获得区块
function ChunkManager:getChunk(x,z)
    -- if x < 0 or x >= self.chunkLength or z < 0 or z >= self.chunkLength then return nil end
    local chunk = self.chunks[x * self.chunkLength + z + self.chunkOffset]
    if chunk then
        return chunk
    else
        return nil
    end
end

function ChunkManager:getBiomeAt(x,z)
    local chunk = self:getChunk(x // 16,z // 16)
    if not chunk then return nil end
    x = x % 16
    z = z % 16
    return Biome.biomeList[chunk.biomeArray[x * 16 + z + 1]]
end

function ChunkManager:getBiomeArrayInChunk(chunkX,chunkZ)
    local biomeNoise = self.biomeNoise:genPerlinNoise2DArray(chunkX,chunkZ,16,16)
    local biomeArray = {}
    for i = 1,256 do
        if biomeNoise[i] > 0.6 then
            biomeArray[i] = Biome.hills.biomeId
        elseif biomeNoise[i] > 0.5 then
            biomeArray[i] = Biome.forest.biomeId
        elseif biomeNoise[i] > 0.3 then
            biomeArray[i] = Biome.plains.biomeId
        else
            biomeArray[i] = Biome.ocean.biomeId
        end
    end
    return biomeArray
end

function ChunkManager:getGrassColor(x,z)
    local n = self.biomeNoise:getPerlinNoise2D(x,z)
    n = (n - 0.5) * 0.3 + 0.8
    return n 
end
